﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace SmartRoute.Data
{
    [Serializable]
    public class CachedRoutingService: IRoutingService, IDeserializationCallback
    {
        private Route[,] _routingTable;
        private Dictionary<Location, int> _dictionary = new Dictionary<Location, int>();

        /// <summary>
        /// Provided so that this class can be extended.
        /// </summary>
        protected CachedRoutingService() 
        {
            redirectRoutingServicePointers();
        }

        /// <summary>
        /// This should be called if this class is deserialized.
        /// </summary>
        public void redirectRoutingServicePointers()
        {
            foreach (var route in _routingTable) route.RoutingService = this; 
        }

        /// <summary>
        /// Caches all routes between pairs of the provided locations in the routing service.
        /// </summary>
        /// <param name="service">The routing service that will generate the cached routes.</param>
        /// <param name="locationsToCache">The locations that will be cached by the routing service.</param>
        /// <returns></returns>
        public static CachedRoutingService CacheService(IRoutingService service, IEnumerable<Location> locationsToCache)
        {
            //Create the reference to the routing service we will return. We do this so that Routes can be set
            // to use the cached routing provider as their routing provider, rather than the service that 
            // generated them.
            CachedRoutingService toProvide = new CachedRoutingService();

            //Put the locations in an array. We only need this here while we're generating the matrix.
            //We only care about distinct locations for the sake of our routing table.
            Location[] locations = locationsToCache.Distinct<Location>().ToArray<Location>();

            //Initialize the dictionary that will be given to toProvide
            Dictionary<Location, int> indices = new Dictionary<Location,int>();

            //Initialize the dictionary to allow efficient "reverse indexing" of locations
            int index = 0;
            foreach (Location location in locations) indices.Add(location, index++);

            //Initialize the shortest distance matrix that provides the cached routes.
            Route[,] matrix = new Route[index, index];
            for (int i = 0; i < index; i++)
            {
                for (int j = 0; j < index; j++)
                {
                    //Generate a route using the provided service.
                    matrix[i, j] = service.FromTo(locations[i], locations[j]);

                    //Make sure this location points to our cached routing service,
                    // and not the original service that generated it.
                    matrix[i, j].RoutingService = toProvide;
                }
            }

            //Construct our cached routing service object and return it.
            toProvide._dictionary = indices;
            toProvide._routingTable = matrix;
            return toProvide;
        }

        /// <summary>
        /// checks if the location is in the cashe
        /// </summary>
        /// <param name="toBeChecked">location to be checked if its in the cashe</param>
        /// <returns></returns>

        public bool CacheHas(Location toBeChecked)
        {
            return _dictionary.ContainsKey(toBeChecked);
        }

        /// <summary>
        /// looks inside the cashe for locations to and from
        /// </summary>
        /// <param name="source"> starting location</param>
        /// <param name="destination"> ending location</param>
        /// <returns></returns>

        public Route FromTo(Location source, Location destination)
        {
            if (_dictionary.ContainsKey(source) && _dictionary.ContainsKey(destination))
                return _routingTable[_dictionary[source], _dictionary[destination]];
            else throw new ArgumentOutOfRangeException
                ("CachedRoutingService.FromTo: Uncached argument must be routed by original service.");
        }


        #region IDeserializationCallback Members
        /// <summary>
        /// deserialize the cashe
        /// </summary>
        /// <param name="sender"></param>
        void IDeserializationCallback.OnDeserialization(object sender)
        {
            this.redirectRoutingServicePointers();
        }

        #endregion
    }
}
